/**
 * 
 */
package com.ra.util;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;

/**
 * 地区地址解析器
 *
 * @author  colin 
 * @version 2014-8-23 
 */
public class AreaParser {
	
	private static final Log logger = LogFactory.getLog(AreaParser.class);
	
	public static String ID_FLAG = "id";
	public static String NAME_FLAG = "name";
	public static String DISTRICT_FLAG = "district";
	public static String CHINA_ID = "1";
	
	private static Document objDocument;
	
	private static String AREA_XML_FILE_PATH = "area.xml";
	
	// area code <--> area
	private static Map<String, List<Area>> codeAreaHierarchy = new HashMap<String, List<Area>>();
	
	// area code <--> address code
	private static Map<String, AddressCode> codeAddressCodeMapping = new HashMap<String, AddressCode>();
	
	// 包括省，市，区各级
	// 省市区全名 --> 多级code的映射
	private static Map<String, String> areaCodeNameMapping = new HashMap<String, String>();
	
	static{
		objDocument = AreaParser.readAreaXMLDocument(AREA_XML_FILE_PATH);
		codeAreaHierarchy = parserAreaDocument(objDocument);
		codeAddressCodeMapping = parserAddressCode();
	}
	
	private AreaParser(){
		
	}
	
	@SuppressWarnings("unchecked")
	private static Map<String, AddressCode> parserAddressCode() {
		String ql = "from " + AddressCode.class.getName() + " a where a.level <= 3";
		try {
			GenericService genericService = ObjectFactory.getInstance().getBean(GenericService.class, false);
			List<AddressCode> addressCodeList = genericService.searchByQl(new QLInfo(ql, true)).getData();
			for (AddressCode addressCode : addressCodeList) {
				String code = buildCode(addressCode);
				AddressCode existed = codeAddressCodeMapping.put(code, addressCode);
				if (existed != null) {
					logger.error("Duplicated code: " + existed.getId());
				}
			}
		} catch(Exception ex) {
			logger.error("Wrong AddressCode", ex);
		}
		return codeAddressCodeMapping;
	}
	
	private static String buildCode(AddressCode addressCode) {
		List<Integer> codes = new ArrayList<>();
		AddressCode parent = addressCode;
		while (parent != null) {
			codes.add(parent.getCode());
			parent = parent.getParent();
		}
		codes.add(1);
		Collections.reverse(codes);
		return StringUtils.join(codes, '_');
	}

	/**
	 * 通过编码获取AddressCode
	 * @param code
	 * @return
	 */
	public static AddressCode getAddressCode(String code) {
		return codeAddressCodeMapping.get(code);
	}
	
	public static Collection<AddressCode> getAddressCodes() {
		return Collections.unmodifiableCollection(codeAddressCodeMapping.values());
	}
	
	private static Map<String, List<Area>> parserAreaDocument(
			Document objDocument) {
		@SuppressWarnings("unchecked")
		List<Element> lstProvinceNodes = objDocument
				.selectNodes("/country/province");
		if (lstProvinceNodes != null && lstProvinceNodes.size() > 0) {
			List<Area> lstProvince = new ArrayList<Area>(64);
			for (Element provinceDetail : lstProvinceNodes) {
				Area provicneArea = new Area();
				
				provicneArea.setAreaId(provinceDetail.element("id").getText());
				provicneArea.setAreaName(provinceDetail.element("name").getText());
				provicneArea.setAreaCode(CHINA_ID+"_"+provicneArea.getAreaId());
				
				List<Area> lstCity = new ArrayList<Area>(64);
				@SuppressWarnings("unchecked")
				List<Element> lstCityNodes = provinceDetail.elements("city");
				if (lstCityNodes != null && lstCityNodes.size() > 0) {
					
					for (Element cityDetail : lstCityNodes) {
						Area cityArea = new Area();
						
						cityArea.setAreaId(cityDetail.element(ID_FLAG).getText());
						cityArea.setAreaName(cityDetail.element(NAME_FLAG).getText());
						cityArea.setAreaCode(provicneArea.getAreaCode()+"_"+cityArea.getAreaId());
						@SuppressWarnings("unchecked")
						List<Element> lstDistrictNodes = cityDetail.elements(DISTRICT_FLAG);
						if (lstDistrictNodes  != null && lstDistrictNodes.size() > 0) {
							List<Area> lstDistrict = new ArrayList<Area>(64);
							for (Element districtDetail : lstDistrictNodes) {
								Area districtArea = new Area();
								
								districtArea.setAreaId(districtDetail.element(
										ID_FLAG).getText());
								districtArea.setAreaName(districtDetail
										.element(NAME_FLAG).getText());
								districtArea.setAreaCode(cityArea.getAreaCode()
										+ "_" + districtArea.getAreaId());
								lstDistrict.add(districtArea);
								areaCodeNameMapping.put(
										districtArea.getAreaCode(),
										provicneArea.getAreaName()
												+ cityArea.getAreaName()
												+ districtArea.getAreaName());
							}
							codeAreaHierarchy.put(cityArea.getAreaCode(), lstDistrict);
							areaCodeNameMapping.put(cityArea.getAreaCode(), provicneArea.getAreaName() + cityArea.getAreaName());
						}
						lstCity.add(cityArea);
					}
					codeAreaHierarchy.put(provicneArea.getAreaCode(), lstCity);
					areaCodeNameMapping.put(provicneArea.getAreaCode(), provicneArea.getAreaName());
				}
				lstProvince.add(provicneArea);
			}
			codeAreaHierarchy.put(CHINA_ID, lstProvince);
			areaCodeNameMapping.put(CHINA_ID, "中国");
		}
		return codeAreaHierarchy;
	}
	
	/**
	 * 通过areaCode获取areaName
	 * @param areaCode
	 * @return
	 */
	public static String getAreaName(String areaCode){
		String areaName = areaCodeNameMapping.get(areaCode);
		return areaName;
	}
	
	public static String findAreaCodeByFullName(String fullAreaName) {
		String code = null;
		for (Entry<String, String> entry : areaCodeNameMapping.entrySet()) {
			String name = entry.getValue();
			if (name.endsWith(fullAreaName)) {
				code = entry.getKey();
				break;
			}
		}
		return code;
	}
	
	public static String buildAreaCode(Integer province, Integer city, Integer district) {
		return buildAreaCode(province, city, district, false);
	}
	
	/**
	 * 
	 * @param province
	 * @param city
	 * @param district
	 * @param municipality 对于直辖市，作为一个整体处理，不再细分
	 * @return
	 */
	public static String buildAreaCode(Integer province, Integer city, Integer district, boolean municipality) {
		StringBuilder addressCode = new StringBuilder(AreaParser.CHINA_ID);
		if (province != null) {
			addressCode.append('_').append(province);
			
			// 北京市、天津市、重庆市、上海市四个直辖市特殊处理
			if (municipality && isMunicipality(province)) {
				return addressCode.toString();
			}
		}
		
		if (city != null) {
			addressCode.append('_').append(city);
		}
		if (district != null) {
			addressCode.append('_').append(district);
		}
		return addressCode.toString();
	}
	
	public static String buildAreaCode(String province, String city, String district, boolean municipality) {
		StringBuilder addressCode = new StringBuilder(AreaParser.CHINA_ID);
		if (province != null && province.length() > 0) {
			addressCode.append('_').append(province);
			
			// 北京市、天津市、重庆市、上海市四个直辖市特殊处理
			if (municipality && isMunicipality(new Integer(province))) {
				return addressCode.toString();
			}
		}
		
		if (city != null && city.length() > 0) {
			addressCode.append('_').append(city);
		}
		if (district != null && district.length() > 0) {
			addressCode.append('_').append(district);
		}
		return addressCode.toString();
	}
	
	public static boolean isMunicipality(Integer province) {
		return province != null && (province == 1 || province == 2 || province == 9 || province == 22);
	}
	
	public static boolean isMunicipality(String province) {
		return province != null && ("北京市".equals(province) || "天津市".equals(province) || "上海市".equals(province) || "重庆市".equals(province)
				|| "北京".equals(province) || "天津".equals(province) || "上海".equals(province) || "重庆".equals(province));
	}
	
	public static String[] findAreaCode(String name) {
		Area area;
		int index = name.indexOf(' ');
		if (index > 0) {
			String firstName = name.substring(0, index);
			area = findAreaCodeByLevel(firstName, null, null, null);
			if (area != null) {
				String[] codeWithCountries = StringUtils.split(area.getAreaCode(), '_');
				int currentLevel = codeWithCountries.length - 1;
				int nextLevel = currentLevel + 1;
				String parentProvince = null;
				String parentCity = null;
				if (AddressCode.LEVEL_PROVINCE.intValue() == currentLevel) {
					parentProvince = codeWithCountries[1];
				} else if (AddressCode.LEVEL_CITY.intValue() == currentLevel) {
					parentProvince = codeWithCountries[1];
					parentCity = codeWithCountries[2];
				}
				if (parentProvince != null) {
					Area subArea = findAreaCodeByLevel(name.substring(index + 1), nextLevel, parentProvince, parentCity);
					if (subArea != null) {
						area = subArea;
					}
				}
			}
		} else {
			area = findAreaCodeByLevel(name, null, null, null);
		}
		return area == null ? null : convert2Codes(area);
	}
	
	private static String[] convert2Codes(Area area) {
		String[] codeWithCountries = StringUtils.split(area.getAreaCode(), '_');
		
		String[] results = new String[3];
		for (int i = 1; i < codeWithCountries.length && i <= 3; i++) {
			results[i - 1] = codeWithCountries[i];
		}
		return results;
	}

	private static Area findAreaCodeByLevel(String name, Integer addressCodeLevel, String parentProvince, String parentCity) {
		if (StringUtils.isEmpty(name)) {
			return null;
		}
		
		Area area = null;
		if (addressCodeLevel == null) {
			area = findAreaCodeByLevel(name, AddressCode.LEVEL_PROVINCE, parentProvince, parentCity);
			if (area == null) {
				area = findAreaCodeByLevel(name, AddressCode.LEVEL_CITY, parentProvince, parentCity);
			}
			if (area == null) {
				area = findAreaCodeByLevel(name, AddressCode.LEVEL_DISTRICT, parentProvince, parentCity);
			}
		} else {
			List<Area> areaList = codeAreaHierarchy.get(CHINA_ID);
			if (AddressCode.LEVEL_PROVINCE.equals(addressCodeLevel)) {
				if (!name.endsWith("省") && !name.endsWith("市")) {
					if (isMunicipality(name)) {
						name = name + "市";
					} else {
						name = name + "省";
					}
				}
				for (Area province : areaList) {
					if (province.getAreaName().equals(name)) {
						area = province;
						break;
					}
				}
			} else if (AddressCode.LEVEL_CITY.equals(addressCodeLevel)) {
				if (!name.endsWith("市") && !name.endsWith("区")) {
					name = name + "市";
				}
				for (Area province : areaList) {
					if (parentProvince != null && !province.getAreaId().equals(parentProvince)) {
						continue;
					}
					
					List<Area> cityList = codeAreaHierarchy.get(province.getAreaCode());
					if (cityList != null) {
						for (Area city : cityList) {
							if (city.getAreaName().equals(name)) {
								area = city;
								break;
							}
						}
					}
					if (area != null) {
						break;
					}
				}
			} else if (AddressCode.LEVEL_DISTRICT.equals(addressCodeLevel)) {
				for (Area province : areaList) {
					if (parentProvince != null && !province.getAreaId().equals(parentProvince)) {
						continue;
					}
					
					List<Area> cityList = codeAreaHierarchy.get(province.getAreaCode());
					String checkName = name;
					if (!name.endsWith("区")) {
						if (!isMunicipality(new Integer(province.getAreaId()))) {
							checkName = name + "区";
						}
					}
					if (cityList != null) {
						for (Area city : cityList) {
							if (parentCity != null && !city.getAreaId().equals(parentCity)) {
								continue;
							}
							
							// 3. district
							List<Area> districtList = codeAreaHierarchy.get(city.getAreaCode());
							if (districtList != null) {
								for (Area district : districtList) {
									if (district.getAreaName().equals(checkName)) {
										area = district;
										break;
									}
								}
							}
						}
						if (area != null) {
							break;
						}
					}
					if (area != null) {
						break;
					}
				}
			}
		}
		return area;
	}
	
	public static Area findProvinceByCity(String cityName) {
		List<Area> areaList = codeAreaHierarchy.get(CHINA_ID);
		String[] codes = new String[3];
		// 1. province
		for (Area province : areaList) {
			List<Area> cityList = codeAreaHierarchy.get(province.getAreaCode());
			if (cityList != null) {
				for (Area city : cityList) {
					if (city.getAreaName().equals(cityName)) {
						return province;
					}
				}
			}
		}
		
		// 直辖市
		for (Area province : areaList) {
			if (province.getAreaName().equals(cityName)) {
				return province;
			}
		}
		return null;
	}
	
	public static String[] findAreaCode(String provinceName, String cityName, String districtName) {
		if (cityName != null && cityName.equals(provinceName)) {		// 直辖市
			cityName = "市辖区";
		}
		
		List<Area> areaList = codeAreaHierarchy.get(CHINA_ID);
		String[] codes = new String[3];
		// 1. province
		for (Area province : areaList) {
			if (provinceName == null || province.getAreaName().equals(provinceName)) {
				codes[0] = province.getAreaId();
				
				// 2. city
				List<Area> cityList = codeAreaHierarchy.get(province.getAreaCode());
				if (cityList != null) {
					for (Area city : cityList) {
						if (city.getAreaName().equals(cityName)) {
							codes[1] = city.getAreaId();
							
							// 3. district
							List<Area> districtList = codeAreaHierarchy.get(city.getAreaCode());
							if (districtList != null) {
								for (Area district : districtList) {
									if (district.getAreaName().equals(districtName)) {
										codes[2] = district.getAreaId();
										break;
									}
								}
							}
							break;
						}
					}
				}
				
				// has province condition or found the city
				if (provinceName != null || codes[1] != null) {
					break;
				}
			}
		}
		return codes;
	}
	
	public static void main(String[] args) {
		List<Area> lst = codeAreaHierarchy.get("1_8");
		for (Area area : lst) {
			System.out.println(area);
		}
	}
	
	public static List<Area> getAreaInfo(String areaCode){
		return codeAreaHierarchy.get(areaCode);
	}
	
	private static Document readAreaXMLDocument(String fileFullName) {
		Document objDocument = null;
		InputStream objInputStream = null;
        objInputStream =
            Thread.currentThread().getContextClassLoader().getResourceAsStream(fileFullName);
        SAXReader objSaxReader = new SAXReader();
        try {
        	objDocument = objSaxReader.read(objInputStream);
        } catch (DocumentException e) {
        	throw new ApplicationException("AreaXML 读取失败，路径：" + AREA_XML_FILE_PATH);
        } finally{
        	IOUtils.closeQuietly(objInputStream);
       }
        return objDocument;
        
	}
	
}
